﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BasicFramework.Enums;
using BasicFramework.Helper;
using System.Diagnostics;

namespace BasicFramework.Scenes
{
    public class SceneManager : ICollection<IScene>, IDisposable
    {
        #region Fields

        private Boolean disposed;
        private List<IScene> scenes;
        private bool readOnly;


        #endregion

        #region Properties

        public List<IScene> Scenes
        {
            get { return scenes; }
        }

        public IScene RunningScene
        {
            get { return this.scenes.Find(x => x.State == SceneState.Running); }
        }

        #endregion

        #region Constructors

        public SceneManager()
        {
            Logger.Info("Initialize Scene Manager");
            this.scenes = new List<IScene>();
            Logger.Info("Scene Manager initialized");
        }


        #endregion

        #region Disposing

        ~SceneManager()
        {
            Dispose(false);
        }



        protected virtual void Dispose(Boolean disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources.
                if (disposing)
                {
                    // Dispose managed resources.

                }

                // Release unmanaged resources. If disposing is false, 
                // only the following code is executed.

            }
            disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion


        #region Events

        public event EventHandler<SceneEventArgs> RunningSceneChanged;
        public event EventHandler<SceneEventArgs> SceneAdded;
        public event EventHandler<SceneEventArgs> SceneRemoved;

        #endregion

        #region Methods

        internal void Update(float frameTime)
        {
            foreach (IScene scene in this.scenes.Where(x => x.State == SceneState.Running))
            {
                // TODO : Performance ? Vllt. Draw und Update doch ins IScene Interface
                Scene scObj = scene as Scene;
                if (scObj != null)
                {
                    scObj.Update(frameTime);
                }
            }
        }

        internal void Draw(float frameTime)
        {
            foreach (IScene scene in this.scenes.Where(x => x.State == SceneState.Running))
            {
                // TODO : Performance ? Vllt. Draw und Update doch ins IScene Interface
                Scene scObj = scene as Scene;
                if (scObj != null)
                {
                    scObj.Draw(frameTime);
                }
            }
        }

        internal void ResourceLoad(bool reloadAll)
        {
            foreach (IScene scene in this.scenes.Where(x => x.State == SceneState.Running))
            {
                Scene scObj = scene as Scene;
                if (scObj != null)
                {
                    scObj.Resume();
                    //scObj.ResourceLoad(reloadAll);
                }
            }
        }

        internal void ResourceUnload(bool reloadAll)
        {
            foreach (IScene scene in this.scenes.Where(x => x.State == SceneState.Running))
            {
                Scene scObj = scene as Scene;
                if (scObj != null)
                {
                    scObj.Paused();
                    //scObj.ResourceUnload(reloadAll);
                }
            }
        }

        public IScene Find(Guid guid)
        {
            return this.Find(String.Empty, guid);
        }

        public IScene Find(string name)
        {
            return this.Find(name, Guid.Empty);
        }

        public IScene Find(string name, Guid guid)
        {
            IScene result = null;

            // Guid and Name specified
            if (name != String.Empty && guid != Guid.Empty)
            {
                result = this.scenes.Find(x => (x.Name == name) && (x.GUID == guid));
            }
            // Name not specified
            if (name == String.Empty && guid != Guid.Empty)
            {
                result = this.scenes.Find(x => x.GUID == guid);
            }
            // Guid not specified
            if (name != String.Empty && guid == Guid.Empty)
            {
                result = this.scenes.Find(x => x.Name == name);
            }
            return result;
        }

        /// <summary>
        /// Starts or Resume the specified Scene. Other Running Scenes will be stopped.
        /// </summary>
        /// <param name="item"></param>
        public void Activate(IScene item)
        {
            if (item == null)
            {
                // TODO: Exception handling
            }
            if (item == this.RunningScene)
            {
                // TODO: Exception Handling
                return;
            }
            Scene scene = item as Scene;

            if (scene != null)
            {
                // Stop former running scene
                if (this.RunningScene != null)
                {
                    Scene former = this.RunningScene as Scene;
                    if (former != null)
                    {
                        former.State = SceneState.Stopped;
                        // Call Stop Method of scene
                        former.Stop();
                    }
                }
                // Check if new Scene was paused
                if (scene.State == SceneState.Paused)
                {
                    // Resume new Scene
                    scene.State = SceneState.Running;
                    scene.Resume();
                }
                else
                {
                    // Start new Scene
                    scene.State = SceneState.Running;
                    scene.Start();
                }

                this.OnRunningSceneChanged(item);
            }
        }

        /// <summary>
        /// Stops the specified Scene.
        /// </summary>
        /// <param name="scene"></param>
        public void StopScene(IScene item)
        {
            if (item == null)
            {
                // TODO: Exception handling
            }
            Scene scene = item as Scene;

            if (scene != null)
            {
                // Check if scene is not already stopped
                if (scene.State != SceneState.Stopped)
                {
                    scene.State = SceneState.Stopped;
                    scene.Stop();
                }

            }
        }


        private void OnSceneAdded(IScene item)
        {
            Logger.Info("Scene " + item.Name + " added");
            if (this.SceneAdded != null)
            {
                this.SceneAdded(this, new SceneEventArgs(item));
            }
        }

        private void OnSceneRemoved(IScene item)
        {
            Logger.Info("Scene " + item.Name + " removed");
            if (this.SceneRemoved != null)
            {
                this.SceneRemoved(this, new SceneEventArgs(item));
            }
        }

        private void OnRunningSceneChanged(IScene scene)
        {
            if (this.RunningSceneChanged != null)
            {
                this.RunningSceneChanged(this, new SceneEventArgs(scene));
            }
        }



        #endregion

        #region ICollection Members

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
        /// </returns>
        /// <filterpriority>1</filterpriority>
        public IEnumerator<IScene> GetEnumerator()
        {
            return this.scenes.GetEnumerator();
        }


        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }



        /// <summary>
        /// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </summary>
        /// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
        public void Add(IScene item)
        {
            if (item == null)
            {
                Logger.Error("Scene item is null");
                Debug.Fail("Scene item is null");
                return;
            }
            if (this.Contains(item))
            {
                Logger.Error("Scene already exists");
                Debug.Fail("Scene already exists");
            }
            else
            {
                this.scenes.Add(item);
                this.OnSceneAdded(item);
                // Initialize the new Scene
                item.Initialize();
            }

        }


        /// <summary>
        /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </summary>
        /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. </exception>
        public void Clear()
        {
            foreach (IScene scene in this.scenes)
            {
                scene.Dispose();
            }
            this.scenes.Clear();
        }

        /// <summary>
        /// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"/> contains a specific value.
        /// </summary>
        /// <returns>
        /// true if <paramref name="item"/> is found in the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false.
        /// </returns>
        /// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
        public bool Contains(IScene item)
        {
            return this.scenes.Contains(item);
        }

        /// <summary>
        /// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
        /// </summary>
        /// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param><param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> is null.</exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than 0.</exception><exception cref="T:System.ArgumentException"><paramref name="array"/> is multidimensional.-or-The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>.-or-Type <paramref name="T"/> cannot be cast automatically to the type of the destination <paramref name="array"/>.</exception>
        public void CopyTo(IScene[] array, int arrayIndex)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </summary>
        /// <returns>
        /// true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </returns>
        /// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
        public bool Remove(IScene item)
        {
            if (this.scenes.Contains(item))
            {
                item.Dispose();
                this.scenes.Remove(item);
                this.OnSceneRemoved(item);
            }
            return false;
        }

        /// <summary>
        /// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </summary>
        /// <returns>
        /// The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </returns>
        public int Count
        {
            get { return this.scenes.Count; }
        }

        /// <summary>
        /// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
        /// </summary>
        /// <returns>
        /// true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false.
        /// </returns>
        public bool IsReadOnly
        {
            get { return this.readOnly; }
        }

        #endregion


    }
}
